from typing import List, Dict, Tuple
from BPR.FileLoader import Interaction
import torch
import numpy as np


class InteractScore:
    def __init__(self, hyper_params):
        self.hyper_params = hyper_params

    def get_interact_score(self, user_interact_list: List[Interaction]) -> Tuple[Dict[int, float], Dict[int, torch.Tensor], Dict[int, Dict[str, str]]]:
        interact_dict: Dict[int, Dict[int, List[Interaction]]] = {}

        # Group By interaction_type and book ID
        for interact in user_interact_list:
            if interact.interaction_type not in interact_dict:
                interact_dict[interact.interaction_type] = {}

            if interact.item_id not in interact_dict[interact.interaction_type]:
                interact_dict[interact.interaction_type][interact.item_id] = []

            interact_dict[interact.interaction_type][interact.item_id].append(
                interact)

        # Process each interaction_type respectively
        result: Dict[int, float] = {}

        # Initialize
        show_info: dict = {}
        for interact in user_interact_list:
            result[interact.item_id] = 0.0
            show_info[interact.item_id] = {}

        extra_info: Dict[int, torch.Tensor] = {}

        for interact_type, book_hist in interact_dict.items():
            if interact_type == 0:
                # enter the reading page
                for book_id, hist in book_hist.items():
                    result[book_id] += min(5.0, float(len(hist)))
                    show_info[book_id]['reader'] = {
                        'text': f'总共阅读了{len(hist)}次书籍',
                        'score': min(5.0, float(len(hist)))
                    }

            elif interact_type == 1:
                # enter the information page
                for book_id, hist in book_hist.items():
                    result[book_id] += min(3.0, 0.5 * float(len(hist)))
                    # half as much as reading
                    show_info[book_id]['info'] = {
                        'text': f'总共进入了{len(hist)}次详情页面',
                        'score': min(3.0, 0.5 * float(len(hist)))
                    }

            elif interact_type == 2:
                # time of staying in recommendation page
                time_max = 0
                for book_id, hist in book_hist.items():
                    for inte in hist:
                        cur_time = inte.info / 1000.0
                        if cur_time > 20.0:
                            cur_time = 20
                        elif cur_time < 0.5:
                            cur_time = 0.5
                        time_max = max(time_max, cur_time)

                for book_id, hist in book_hist.items():
                    current_result = 0.0
                    stay_time = 0.0
                    for inte in hist:
                        cur_time = inte.info / 1000.0
                        if cur_time > 20.0:
                            cur_time = 20
                        elif cur_time < 0.5:
                            cur_time = 0.5
                        current_result += cur_time / time_max
                        stay_time += cur_time
                    current_result /= len(hist)
                    current_result = current_result * 4.0 - 1.0
                    result[book_id] += current_result
                    # The mean stay time linearly nomalized to [-2,2]
                    show_info[book_id]['stay'] = {
                        'text': f'在书籍平均停留{stay_time}秒',
                        'score': current_result
                    }

            elif interact_type == 3:  # scroll speed
                sta_hist = []
                book_id_list = []

                for book_id, hist in book_hist.items():
                    speed_array = []
                    for inte in hist:
                        speed_array.append(inte.info)
                    min_val = 0
                    max_val = np.max(speed_array)
                    cnt_array = [0] * 21
                    for i in speed_array:
                        cnt_array[int(
                            (i-min_val)/(max_val-min_val+1e-7)*20)] += 1
                    current_result = np.max(cnt_array) / len(hist)
                    sta_hist.append(current_result)
                    book_id_list.append(book_id)

                sta_mean = np.mean(sta_hist)
                sta_std = np.std(sta_hist)
                std_hist = [(i - sta_mean) / (sta_std+1e-7) for i in sta_hist]

                for book_id, score, sta in zip(book_id_list, std_hist, sta_hist):
                    current_score = score * 3.0
                    # Suppose that there are no negative velocity (or bincount will occur error)
                    # np.argmax(np.bincount(speed_array)) is the stability
                    # stability/len(hist) is the frequency
                    # The answer will be always smaller than 0, maybe we need a large weight
                    result[book_id] += current_score
                    show_info[book_id]['speed'] = {
                        'text': f'滑动稳定性为{sta}',
                        'score': current_score
                    }

            elif interact_type == 4:
                # user score
                for book_id, hist in book_hist.items():
                    current_result = 0.0
                    for inte in hist:
                        current_result += inte.info - 3
                    current_result /= len(hist)
                    # The mean value of the user score of the book (one user may score a book many times)
                    result[book_id] += current_result
                    show_info[book_id]['rate'] = {
                        'text': f'对书籍平均评价为{current_result+3}分',
                        'score': current_result
                    }

            elif interact_type == 5:
                # user like it
                for book_id, hist in book_hist.items():
                    result[book_id] += 2
                    # the return value can be same as full mark
                    show_info[book_id]['zan'] = {
                        'text': f'用户对推荐的物品点赞',
                        'score': 2
                    }

            elif interact_type == 6:
                # user dislike it
                for book_id, hist in book_hist.items():
                    result[book_id] -= 2
                    # return 0 or a negative value
                    show_info[book_id]['cai'] = {
                        'text': f'用户对推荐的物品点踩',
                        'score': -2
                    }

            # Calculate Total
            for k, v in result.items():
                if v >= 3.0:
                    text = '强烈推荐👍'
                elif v >= 0.0:
                    text = '弱推荐'
                elif v < 0.0:
                    text = '不推荐👎'
                show_info[k]['total'] = {
                    'score': v,
                    'text': text
                }

            # Extra Info (Calculate Average Values)
            for book_id, hist in book_hist.items():
                cur_extra = torch.zeros(
                    self.hyper_params['extra_dim'], dtype=torch.float32)
                cur_extra_cnt = 0
                for interact in hist:
                    if interact.extra_info is not None:
                        cur_extra += torch.FloatTensor(interact.extra_info)
                        cur_extra_cnt += 1

                if cur_extra_cnt > 0:
                    cur_extra /= float(cur_extra_cnt)

                extra_info[book_id] = cur_extra

        return result, extra_info, show_info
